/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
 * Description: UDK timer file
 * Author: -
 * Create: 2021.07.01
 */
#ifndef UDK_ARCH_ARM64
#include <cpuid.h>
#endif
#include <unistd.h>
#include <fcntl.h>
#include <inttypes.h>

#include "udk.h"
#include "udk_cycles.h"
#include "udk_log.h"

static uint64_t udk_tsc_resolution_hz;

void udk_delay_us(uint32_t us)
{
    const uint64_t start = udk_get_timer_cycles();
    const uint64_t ticks = (uint64_t)us * udk_get_timer_hz() / 1000000;

    while ((udk_get_timer_cycles() - start) < ticks) {
        udk_pause();
    }
}

void udk_delay_ms(uint32_t ms)
{
    udk_delay_us(ms * USEC_PER_MSEC);
}

/* Timer initialization */
#ifdef UDK_ARCH_ARM64

static uint64_t udk_get_tsc_freq_arch(void)
{
    return udk_arm64_cntfrq();
}

#else

static uint32_t udk_cpu_get_model(uint32_t fam_mod_step)
{
    uint32_t family_id, model_id, ext_model_id;

    family_id = (fam_mod_step >> 0x8) & 0xf;
    model_id = (fam_mod_step >> 0x4) & 0xf;

    if (family_id == 0x6 || family_id == 0xf) {
        ext_model_id = (fam_mod_step >> 0x10) & 0xf;
        model_id += (ext_model_id << 0x4);
    }

    return model_id;
}

static uint32_t udk_check_model_wsm_nhm(uint8_t model)
{
    switch (model) {
        /* Westmere */
        case 0x25:
        /* Nehalem */
        case 0x1E:
        /* Westmere */
        case 0x2C:
        /* Nehalem */
        case 0x1F:
        /* Westmere */
        case 0x2F:
        /* Nehalem */
        case 0x1A:
        case 0x2E:
            return 1;
        default:
            return 0;
    }
}

static uint32_t udk_check_model_gdm_dnv(uint8_t model)
{
    switch (model) {
        /* Denverton */
        case 0x5F:
        /* Goldmont */
        case 0x5C:
            return 1;
        default:
            return 0;
    }
}

static int32_t udk_rdmsr(int msr_offset, uint64_t *value)
{
    int fd, rc;

    fd = open("/dev/cpu/0/msr", O_RDONLY);
    if (fd < 0) {
        return fd;
    }

    rc = pread(fd, value, sizeof(uint64_t), msr_offset);

    close(fd);

    return rc;
}

#define TSC_TO_FREQ_HZ(tsc_hz, mult) ((((tsc_hz) >> 8) & 0xff) * (mult) * 1000000)

static uint64_t udk_get_tsc_freq_arch(void)
{
    uint64_t tsc_hz = 0;
    uint32_t a, b, c, d, maxleaf;
    uint8_t mult, model;
    int32_t ret;

    maxleaf = __get_cpuid_max(0, NULL);
    if (maxleaf >= 0x15) {
        __cpuid(0x15, a, b, c, d);

        if (b != 0 && c != 0) {
            return (uint64_t)c * (b / a);
        }
    }

    __cpuid(0x1, a, b, c, d);
    model = (uint8_t)udk_cpu_get_model(a);
    if (udk_check_model_wsm_nhm(model) != 0) {
        mult = 133; // wsm or nhm mult 133.
    } else if ((c & (uint32_t)bit_AVX) || udk_check_model_gdm_dnv(model) != 0) {
        mult = 100; // gdm or dnv mult 100.
    } else {
        return 0;
    }

    ret = udk_rdmsr(0xCE, &tsc_hz);
    if (ret < 0) {
        return 0;
    }

    return TSC_TO_FREQ_HZ(tsc_hz, mult);
}
#endif

static uint64_t udk_get_tsc_freq(void)
{
#ifdef CLOCK_MONOTONIC_RAW
#define NS_PER_SEC 1E9
#define CYC_PER_10MHZ 1E7

    struct timespec sleeptime = {
        .tv_nsec = NS_PER_SEC / 10 /* 1/10 second */
    };

    struct timespec t_start, t_end;
    uint64_t tsc_hz;

    if (clock_gettime(CLOCK_MONOTONIC_RAW, &t_start) == 0) {
        uint64_t ns, end, start = udk_rdtsc();
        nanosleep(&sleeptime, NULL);
        clock_gettime(CLOCK_MONOTONIC_RAW, &t_end);
        end = udk_rdtsc();
        ns = ((t_end.tv_sec - t_start.tv_sec) * NS_PER_SEC);
        ns += (t_end.tv_nsec - t_start.tv_nsec);

        double secs = (double)ns / NS_PER_SEC;
        tsc_hz = (uint64_t)((end - start) / secs);
        /* Round up to 10Mhz. 1E7 ~ 10Mhz */
        return UDK_ALIGN_MUL_NEAR(tsc_hz, CYC_PER_10MHZ);
    }
#endif
    return 0;
}

static uint64_t udk_estimate_tsc_freq(void)
{
#define CYC_PER_10MHZ 1E7
    UDK_LOG(WARNING, COMMON, "WARNING: TSC frequency estimated roughly - clock timings may be less accurate.\n");
    /* assume that the sleep(1) will sleep for 1 second */
    uint64_t start = udk_rdtsc();
    uint64_t end;
    (void)sleep(1);
    end = udk_rdtsc() - start;
    /* Round up to 10Mhz */
    return (uint64_t)UDK_ALIGN_MUL_NEAR(end, CYC_PER_10MHZ);
}

static void udk_set_tsc_freq(void)
{
    struct udk_mem_config *mcfg = udk_get_configuration()->mem_config;
    uint64_t freq;

    if (udk_process_type() == UDK_PROC_SECONDARY) {
        /* use primary process TSC rate */
        udk_tsc_resolution_hz = mcfg->tsc_hz;
        return;
    }

    freq = udk_get_tsc_freq_arch();
    if (freq == 0) {
        freq = udk_get_tsc_freq();
    }
    if (freq == 0) {
        freq = udk_estimate_tsc_freq();
    }

    UDK_LOG(DEBUG, COMMON, "TSC frequency is ~%"PRIu64" KHz\n", freq / 1000);
    udk_tsc_resolution_hz = freq;
    mcfg->tsc_hz = freq;
}

uint64_t udk_get_tsc_hz(void)
{
    return udk_tsc_resolution_hz;
}

int udk_timer_init(void)
{
    udk_set_tsc_freq();
    return 0;
}
/* End of timer initialization */
